package org.vaadin.nf4v.ext.runtime.menu.menubar;

import com.vaadin.terminal.Resource;
import com.vaadin.terminal.ThemeResource;
import com.vaadin.ui.MenuBar;
import org.vaadin.nf4v.core.NavigableApplication;
import org.vaadin.nf4v.core.config.ContextConfig;
import org.vaadin.nf4v.core.config.NavigationConfig;
import org.vaadin.nf4v.core.config.PageConfig;
import org.vaadin.nf4v.ext.config.menu.ExtMenuUtil;
import org.vaadin.nf4v.ext.config.menu.MenuItemDescription;

/**
 * Base menu builder implementation.
 * Depends on menu configuration extension (MenuAnnotationParser should be registered and pages should be
 * annotated with @Menu annotation)
 * <p/>
 * Extend this to implement additional menu building aspects (name localization, security, icons handling)
 */
public class MenuBuilderImpl {

    /**
     * Builds entire application menu (including all contexts).
     * Note: make sure that all menu items are marked as context insensitive (@Menu(contextSensitive=false)
     * in other way automatic context switching will not work (framework will try to find page in current context).
     * Note: includeContextNodes affects only if window classes were also annotated with @Menu annotation
     *
     * @param config              navigation config
     * @param includeContextNodes true to include context nodes in menu building
     * @return menu bar
     */
    public MenuBar buildFullMenuTree(NavigationConfig config, boolean includeContextNodes) {
        MenuBar bar = new MenuBar();
        MenuItemDescription root = ExtMenuUtil.getMenuItem(config);
        if (root == null) return bar;

        for (MenuItemDescription description : root.getChilds()) {
            if (includeContextNodes && description.getName() != null) {
                buildSubtree(bar, description);
            } else {
                for (MenuItemDescription item : description.getChilds()) {
                    buildSubtree(bar, item);
                }
            }
        }
        return bar;
    }

    /**
     * Builds menu tree for context.
     * Note: includeContextNode affects only if window class were also annotated with @Menu annotation
     *
     * @param config             context config
     * @param includeContextNode true to include context node in menu building
     * @return menu bar
     */
    public MenuBar buildContextMenuTree(ContextConfig config, boolean includeContextNode) {
        MenuBar bar = new MenuBar();
        MenuItemDescription description = ExtMenuUtil.getMenuItem(config);
        if (includeContextNode && description.getName() != null) {
            buildSubtree(bar, description);
        } else {
            for (MenuItemDescription item : description.getChilds()) {
                buildSubtree(bar, item);
            }
        }
        return bar;
    }

    /**
     * Builds menu tree for current page
     *
     * @param config            page config
     * @param includingThisPage true to include page itself as root menu item
     * @return menu bar
     */
    public MenuBar buildPageMenuTree(PageConfig config, boolean includingThisPage) {
        MenuBar bar = new MenuBar();
        MenuItemDescription description = ExtMenuUtil.getMenuItem(config);
        if (includingThisPage) {
            buildSubtree(bar, description);
        } else {
            for (MenuItemDescription item : description.getChilds()) {
                buildSubtree(bar, item);
            }
        }
        return bar;
    }

    /**
     * Menu building implementation.
     *
     * @param root        menu bar
     * @param description starting page
     */
    protected void buildSubtree(MenuBar root, MenuItemDescription description) {
        MenuBar.MenuItem menu = root.addItem(getItemName(description), null);
        if (description.isCommandItem()) {
            menu.setCommand(getItemCommand(description));
        }
        if (root.getIcon() != null) {
            menu.setIcon(getItemIcon(description));
        }
        handleNewMenuItem(menu, description);

        boolean first = true;
        for (MenuItemDescription child : description.getChilds()) {
            buildSubtree(menu, child, first);
            first = false;
        }
    }

    /**
     * Menu building implementation.
     *
     * @param root        menu item
     * @param description starting item
     * @param first       true if this item should be the first on this menu item (and if it's not
     *                    separators may be added according to @Menu(separate=true) configuration)
     */
    protected void buildSubtree(MenuBar.MenuItem root, MenuItemDescription description, boolean first) {
        if (!first && description.isSeparate()) {
            root.addSeparator();
        }
        MenuBar.MenuItem menu = root.addItem(getItemName(description), null);
        if (description.isCommandItem()) {
            menu.setCommand(getItemCommand(description));
        }
        if (root.getIcon() != null) {
            menu.setIcon(getItemIcon(description));
        }
        handleNewMenuItem(menu, description);

        boolean firstInSubmenu = true;
        for (MenuItemDescription child : description.getChilds()) {
            buildSubtree(menu, child, firstInSubmenu);
            firstInSubmenu = false;
        }
    }

    /**
     * Override this to change default menu names logic (e.g. to add localization)
     *
     * @param description menu item description
     * @return menu item name
     */
    protected String getItemName(MenuItemDescription description) {
        return description.getPath()[description.getPath().length - 1];
    }

    /**
     * Override this to change Resource implementation.
     * It is called only for items with not empty icon path.
     *
     * @param description menu item description
     * @return item icon resource
     */
    protected Resource getItemIcon(MenuItemDescription description) {
        return new ThemeResource(description.getIcon());
    }

    /**
     * Override this to change (extend) default command behaviour.
     * It is called only for page-related items (it is not called for "empty" submenu roots,
     * created during menu items path's)
     *
     * @param description menu item description
     * @return menu item command
     */
    protected MenuBar.Command getItemCommand(MenuItemDescription description) {
        return new NavigationCommand(description);
    }

    /**
     * Override this to postprocess just created menu items (e.g. apply security restrictions).
     *
     * @param menuItem    menu item
     * @param description menu item description
     */
    protected void handleNewMenuItem(MenuBar.MenuItem menuItem, MenuItemDescription description) {
    }
}
